package com.gin.no_gay_alliance.worker;

import android.content.Context;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.work.BackoffPolicy;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import androidx.work.WorkRequest;
import androidx.work.Worker;
import androidx.work.WorkerParameters;

import com.gin.common.utils.TimeUtils;
import com.gin.nga.client.NgaClient;
import com.gin.nga.exception.NgaServerException;
import com.gin.nga.method.ReadApi;
import com.gin.nga.params.read.ReadTopicParam;
import com.gin.nga.response.body.ReadBody;
import com.gin.no_gay_alliance.app.MyApplication;
import com.gin.no_gay_alliance.database.dao.ScanTaskDao;
import com.gin.no_gay_alliance.database.entity.ScanTask;
import com.gin.no_gay_alliance.database.manager.ReplyHistoryManager;
import com.gin.no_gay_alliance.viewmodel.ReadItem;

import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 回复历史扫描工作
 *
 * @author : ginstone
 * @version : v1.0.0
 * @since : 2023/9/20 15:39
 **/
public class ScanTaskWorker extends Worker {
    /**
     * 最大重试次数
     */
    private static final int MAX_ATTEMPT_COUNT = 5;
    private static final String TAG = ScanTaskWorker.class.getSimpleName();
    /**
     * 运行参数key： 任务id
     */
    private static final String ARG_ID = "id";
    /**
     * 运行参数key： 扫描目标页
     */
    private static final String ARG_PAGE = "page";
    /**
     * 运行参数key： 主题id
     */
    private static final String ARG_TOPIC_ID = "topicId";
    /**
     * 主题id
     */
    private final long topicId;
    /**
     * 扫描页数
     */
    private final int page;
    /**
     * 任务id
     */
    private final int id;
    private MyApplication app;
    private ScanTaskDao dao;


    public ScanTaskWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);

        Data data = workerParams.getInputData();
        this.topicId = data.getLong(ARG_TOPIC_ID, -1);
        this.page = data.getInt(ARG_PAGE, 1);
        this.id = data.getInt(ARG_ID, -1);
    }

    @NonNull
    @Override
    public Result doWork() {
        app = (MyApplication) getApplicationContext();
        dao = app.ngaDatabase.scanTaskDao();
        try {
            onSuccess(scan(topicId, page));
            return Result.success();
        } catch (Exception e) {
            // 重试次数小于最大次数时，重试
            if (getRunAttemptCount() < MAX_ATTEMPT_COUNT) {
                Log.d(TAG, "执行失败, 重试: " + getRunAttemptCount());
                return Result.retry();
            }
            // 否则记录最后一次失败原因，任务失败
            ScanTask scanTask = dao.get(id);
            scanTask.status = ScanTask.Status.ERROR;
            scanTask.description = buildDescription("任务失败", String.format("%s: %s", e.getClass().getSimpleName(), e.getMessage()));
            dao.update(scanTask);
            return Result.failure();
        }
    }

    /**
     * 执行成功
     *
     * @param body 响应
     */
    private void onSuccess(ReadBody body) {
        //写入回复历史
        ReadItem readItem = new ReadItem(body);
        ReplyHistoryManager.addReplyHistories(body);

        // 扫描任务
        ScanTask scanTask = dao.get(id);
        // 更新最大页数
        scanTask.totalPage = readItem.totalPage;
        // 判断任务是否结束
        int endPage = scanTask.pageEnd != null ? scanTask.pageEnd : scanTask.totalPage;
        if (readItem.page >= endPage) {
            // 任务结束, 更新状态和描述
            scanTask.status = ScanTask.Status.COMPLETED;
            scanTask.description = buildDescription("任务结束", "");
        } else {
            // 任务未结束, 更新页数，再次提交任务
            scanTask.pageStart = readItem.page + 1;
            scanTask.description = buildDescription("扫描成功", "页数: " + scanTask.pageStart);

            // 如果任务仍在进行中，继续提交任务
            if (scanTask.status == ScanTask.Status.IN_PROGRESS) {
                scanTask.uuid = enqueue(app, scanTask);
            }
        }
        // 更新任务
        dao.update(scanTask);
    }

    /**
     * 打包运行参数
     *
     * @param topicId 主题id
     * @param page    目标页
     * @param id      任务id
     * @return data
     */
    public static Data buildData(long topicId, int page, int id) {
        Data.Builder builder = new Data.Builder();
        builder.putLong(ARG_TOPIC_ID, topicId);
        builder.putInt(ARG_PAGE, page);
        builder.putInt(ARG_ID, id);
        return builder.build();
    }

    /**
     * 提交一个工作到队列
     *
     * @param task 任务
     * @return uuid
     */
    public static UUID enqueue(MyApplication app, @NonNull ScanTask task) {
        Log.d(TAG, String.format(Locale.CHINA, "提交工作: %s tid: %d page: %d", task.title, task.topicId, task.pageStart));
        //创建工作请求
        WorkRequest workRequest = buildWorkRequest(task);
        //提交任务
        WorkManager.getInstance(app).enqueue(workRequest);
        return workRequest.getId();
    }

    /**
     * 查找一个任务并启动动作
     */
    public static void startWorker() {
        MyApplication app = MyApplication.app;
        // 清空本类 其他任务
        WorkManager.getInstance(app).cancelAllWorkByTag(ScanTaskWorker.class.getName());

        ScanTaskDao dao = app.ngaDatabase.scanTaskDao();
        WorkManager workManager = WorkManager.getInstance(app);
        List<ScanTask> scanTasks = dao.listAll();

        // 查找第一个正在执行的任务 ，如果工作存在且失败或取消，重新提交
        ScanTask taskInProgress = scanTasks.stream().filter(i -> i.status == ScanTask.Status.IN_PROGRESS).findFirst().orElse(null);
        if (taskInProgress != null) {
            try {
                WorkInfo workInfo = workManager.getWorkInfoById(taskInProgress.uuid).get();
                // 如果失败或被取消，重新提交
                switch (workInfo.getState()) {
                    case FAILED:
                    case CANCELLED:
                        taskInProgress.uuid = enqueue(app, taskInProgress);
                        dao.update(taskInProgress);
                        break;
                }
            } catch (ExecutionException | InterruptedException e) {
                e.printStackTrace();
            }
            return;
        }
        // 查找等待中的任务, 如果存在修改其为进行中任务，提交工作
        ScanTask taskWaiting = scanTasks.stream().filter(i -> i.status == ScanTask.Status.WAITING).findFirst().orElse(null);
        if (taskWaiting != null) {
            taskWaiting.status = ScanTask.Status.IN_PROGRESS;
            taskWaiting.uuid = enqueue(app, taskWaiting);
            dao.update(taskWaiting);
            return;
        }

        Log.d(TAG, "没有需要启动的任务");
    }

    /**
     * 创建工作请求
     *
     * @param task 任务
     * @return 工作请求
     */
    public static WorkRequest buildWorkRequest(@NonNull ScanTask task) {
        // 连上网络才执行
        Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();

        return new OneTimeWorkRequest.Builder(ScanTaskWorker.class).setInputData(buildData(task.topicId, task.pageStart, task.id)).setConstraints(constraints)
                // 延迟15秒执行
                .setInitialDelay(15, TimeUnit.SECONDS)
                // 重试策略
                .setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS).build();
    }

    /**
     * 扫描一页，返回响应
     *
     * @param topicId 主题id
     * @param page    页码
     * @return 响应
     */
    public static ReadBody scan(long topicId, int page) throws IOException {
        NgaClient client = MyApplication.getClient();
        ReadTopicParam param = new ReadTopicParam(topicId, page);
        try {
            return ReadApi.readTopic(client, param).sync();
        } catch (IOException e) {
            if (e instanceof NgaServerException && ((NgaServerException) e).getCode() == 500) {
                return ReadApi.readTopicDoc(client, param).sync();
            }
            throw e;
        }
    }

    private static String buildDescription(String tag, String message) {
        String datetime = TimeUtils.format(ZonedDateTime.now(), TimeUtils.SHORT_FORMATTER);
        return String.format("%s [%s] %s", datetime, tag, message);
    }
}
